using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Elte.SqlSpIdx.Parser
{
    public class Tokenizer
    {
        private enum State
        {
            Between,
            Inword,
            ReadDigits,
            AfterDot
        }

        public List<Token> GetTokenList(string s)
        {
            List<Token> tokenlist = new List<Token>();

            State state = State.Between;
            string currtoken = "";
            int currtokenstartpos = 0;
            int i = 0;

            while (i <= s.Length)
            {
                char c;
                if (i < s.Length)
                    c = s[i];
                else
                    c = ' '; // EOS is a ' '
                if (state == State.Between)
                {
                    if (char.IsWhiteSpace(c))
                    { }
                    else if (c == '+')
                        tokenlist.Add(new Sign(i, true));
                    else if (c == '-')
                        tokenlist.Add(new Sign(i, false));
                    else if (c == '*')
                        tokenlist.Add(new Times(i));
                    else if (c == '<')
                        tokenlist.Add(new LessOrGreaterThan(i, true));
                    else if (c == '>')
                        tokenlist.Add(new LessOrGreaterThan(i, false));
                    else if (char.IsLetter(c))
                    {
                        currtoken += c;
                        currtokenstartpos = i;
                        state = State.Inword;
                    }
                    else if (char.IsDigit(c))
                    {
                        currtoken += c;
                        currtokenstartpos = i;
                        state = State.ReadDigits;
                    }
                    else
                        throw new TokenizerException(i, "");
                }
                else if (state == State.Inword)
                {
                    if (c == '+')
                    {
                        if (currtoken == "AND")
                            tokenlist.Add(new AND(currtokenstartpos));
                        else
                            tokenlist.Add(new Variable(currtokenstartpos, currtoken));
                        currtoken = "";
                        tokenlist.Add(new Sign(i, true));
                        state = State.Between;
                    }
                    else if (c == '-')
                    {
                        if (currtoken == "AND")
                            tokenlist.Add(new AND(currtokenstartpos));
                        else
                            tokenlist.Add(new Variable(currtokenstartpos, currtoken));
                        currtoken = "";
                        tokenlist.Add(new Sign(i, false));
                        state = State.Between;
                    }
                    else if (c == '*')
                    {
                        if (currtoken == "AND")
                            tokenlist.Add(new AND(currtokenstartpos));
                        else
                            tokenlist.Add(new Variable(currtokenstartpos, currtoken));
                        currtoken = "";
                        tokenlist.Add(new Times(i));
                        state = State.Between;
                    }
                    else if (c == '<')
                    {
                        if (currtoken == "AND")
                            tokenlist.Add(new AND(currtokenstartpos));
                        else
                            tokenlist.Add(new Variable(currtokenstartpos, currtoken));
                        currtoken = "";
                        tokenlist.Add(new LessOrGreaterThan(i, true));
                        state = State.Between;
                    }
                    else if (c == '>')
                    {
                        if (currtoken == "AND")
                            tokenlist.Add(new AND(currtokenstartpos));
                        else
                            tokenlist.Add(new Variable(currtokenstartpos, currtoken));
                        currtoken = "";
                        tokenlist.Add(new LessOrGreaterThan(i, false));
                        state = State.Between;
                    }
                    else if (char.IsLetter(c) || char.IsDigit(c))
                    {
                        currtoken += c;
                    }
                    else if (char.IsWhiteSpace(c))
                    {
                        if (currtoken == "AND")
                            tokenlist.Add(new AND(currtokenstartpos));
                        else
                            tokenlist.Add(new Variable(currtokenstartpos, currtoken));
                        currtoken = "";
                        state = State.Between;
                    }
                    else
                        throw new TokenizerException(i, "");
                }
                else if (state == State.ReadDigits)
                {
                    if (c == '+')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new Sign(i, true));
                        state = State.Between;
                    }
                    else if (c == '-')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new Sign(i, false));
                        state = State.Between;
                    }
                    else if (c == '*')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new Times(i));
                        state = State.Between;
                    }
                    else if (c == '<')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new LessOrGreaterThan(i, true));
                        state = State.Between;
                    }
                    else if (c == '>')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new LessOrGreaterThan(i, false));
                        state = State.Between;
                    }
                    else if (c == '.')
                    {
                        currtoken += c;
                        state = State.AfterDot;
                    }
                    else if (char.IsDigit(c))
                    {
                        currtoken += c;
                    }
                    else if (char.IsWhiteSpace(c))
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        state = State.Between;
                    }
                    else
                        throw new TokenizerException(i, "Invalid number");
                }
                else if (state == State.AfterDot)
                {
                    if (c == '+')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new Sign(i, true));
                        state = State.Between;
                    }
                    else if (c == '-')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new Sign(i, false));
                        state = State.Between;
                    }
                    else if (c == '*')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new Times(i));
                        state = State.Between;
                    }
                    else if (c == '<')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new LessOrGreaterThan(i, true));
                        state = State.Between;
                    }
                    else if (c == '>')
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken)));
                        currtoken = "";
                        tokenlist.Add(new LessOrGreaterThan(i, false));
                        state = State.Between;
                    }
                    else if (char.IsDigit(c))
                    {
                        currtoken += c;
                    }
                    else if (char.IsWhiteSpace(c))
                    {
                        tokenlist.Add(new Number(currtokenstartpos, double.Parse(currtoken, System.Globalization.CultureInfo.InvariantCulture.NumberFormat)));
                        currtoken = "";
                        state = State.Between;
                    }
                    else
                        throw new TokenizerException(i, "");
                }
                else
                {
                    throw new TokenizerException(-1, "don't know what to do with state: " + state);
                }
                i++;
            }

            tokenlist.Reverse();
            return tokenlist;
        }

        /*
        private bool IsAlpha(char c)
        {
            Regex pat = new Regex("[a-zA-Z_]");
            return pat.IsMatch(c.ToString());
        }

        private bool IsDigit(char c)
        {
            Regex pat = new Regex("[0-9]");
            return pat.IsMatch(c.ToString());
        }

        private bool IsWhiteSpace(char c)
        {
            if ((c == ' ') || (c == '\t'))
                return true;
            else
                return false;
        }
         * */
    }

    public class TokenizerException : Exception
    {
        int position;
        string message;

        public TokenizerException(int position, string message)
        {
            this.position = position;
            this.message = "tokenizer: " + message;
        }
    }
}
